Skip to content

refactor(agent): remove CherryClaw branding and the Soul Mode toggle#16726

Merged
0xfullex merged 62 commits into
mainfrom
remove-cherryClaw
Jul 9, 2026
Merged

refactor(agent): remove CherryClaw branding and the Soul Mode toggle#16726
0xfullex merged 62 commits into
mainfrom
remove-cherryClaw

Conversation

@vaayne

@vaayne vaayne commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Before this PR:

  • Agents had a Soul Mode / Autonomous Mode toggle (configuration.soul_enabled). Only soul-enabled agents got the claw/autonomy MCP tools, the agent-memory MCP server, the workspace persona system prompt (SOUL.md / USER.md / FACT.md), and the autonomous-operation disallowed-tools overlay. Enabling it force-switched the agent to bypassPermissions, and scheduled tasks / channels refused agents without it.
  • CherryClaw branding lingered across the repo: a src/main/ai/agents/cherryclaw/ directory (the engine actually powering soul mode for every agent), a docs/references/cherryclaw/ doc set, brand mentions in system prompts and channel help text, and an agent.cherryClaw.* i18n namespace.

After this PR:

  • Soul-mode behavior is unconditional for every agent: the cherry-tools autonomy tools + agent-memory MCP server are always injected and allow-listed, the PromptBuilder workspace prompt always replaces the SDK preset, and all agents are eligible for scheduled tasks and channels.
  • The soul disallowed-tools overlay is deleted outright: agents keep AskUserQuestion, plan-mode, and worktree tools where the tool registry allows them (Cron* / TodoWrite / NotebookEdit remain blocked by their pre-existing exposure: 'disabled' registry classification). Exception added after review: headless runs (channel-triggered and scheduled-task dispatches) still disallow AskUserQuestion — they have no responder, so a question would stall the run.
  • Review hardening on top of the removal: the auto-approved config tool now rejects channels owned by another agent; notify / config are user-disableable (exposure: 'user') like cron; agents with non-blank user instructions skip the bootstrap onboarding interview; the prompt no longer references the unmounted mcp__skills__skills tool.
  • The soul_enabled flag is fully removed: no UI toggle, no zod schema field, no reads anywhere. permission_mode is now a fully independent setting and its field is always visible in the agent edit dialog.
  • CherryClaw branding is gone: the cherryclaw/ engine directory is flattened into src/main/ai/agents/ (prompt / bootstrap / heartbeat), prompts and channel help are de-branded, the CherryClaw doc set is deleted, and agent.cherryClaw.channels.* / agent.cherryClaw.tasks.* i18n keys are flattened to agent.channels.* / agent.tasks.* across all locales.
  • The old claw MCP server is merged into cherry-tools; autonomy tools now live under mcp__cherry-tools__cron, mcp__cherry-tools__notify, and mcp__cherry-tools__config.
  • Dead code removed along the way: seedWorkspaceTemplates, PromptBuilder.buildToolGuidance, and the always-true branches of composeToolGuidance / adjustAllowedToolsForMcp.

Fixes # N/A

Why we need it and why it was done in this way

CherryClaw's autonomy features were productized as soul mode and recent work already made personality + autonomy the default for new agents. Keeping a toggle (and a brand) for behavior that every agent should have added a second code path, a confusing permission-mode coupling, stale docs, and tool-name drift. Removing the flag and merging the autonomy tools into cherry-tools gives the app one agent behavior instead of two.

The following tradeoffs were made:

  • The soul overlay's interactive-tool bans are not carried over: AskUserQuestion, plan-mode, and worktree tools stay available to every agent unless the tool registry disables them. The assistant role's pre-existing AskUserQuestion disable is preserved explicitly, and headless (channel / scheduled-task) runs keep AskUserQuestion disallowed via a transient dispatch flag.
  • Data-migration shims that reference the legacy name are intentionally kept: the 'cherry-claw''claude-code' read-time type remap in AgentService and the 'cherry-claw-default' id remap migrator. The unrelated cherry-claw.png model icon is untouched.
  • The stored soul_enabled configuration key is simply ignored (the configuration zod schema is .loose()), so no data migration is needed.
  • Existing references to old MCP tool names (mcp__claw__*) are not kept as aliases; callers must use the new mcp__cherry-tools__* names.

The following alternatives were considered:

  • Renaming cherryclaw/ to workspace/ or persona/ instead of flattening — rejected: workspace is heavily overloaded in this codebase and the directory only held a few agent-engine modules.
  • Keeping the toggle but defaulting it on — rejected: leaves the dual code path and the permission-mode coupling in place.
  • Keeping a separate claw MCP server name as a compatibility alias — rejected: this PR removes the CherryClaw product surface rather than preserving a legacy synonym.

Links to places where the discussion took place: N/A

Breaking changes

The Soul Mode / Autonomous Mode toggle is removed from agent settings; every agent now always runs with autonomy features (scheduled tasks, channels, cron/notify/config tools, agent-memory tools, workspace persona prompt). Scheduled tasks and channels no longer require Soul Mode or bypassPermissions.

MCP tool names changed from mcp__claw__* to mcp__cherry-tools__*:

  • mcp__claw__cronmcp__cherry-tools__cron
  • mcp__claw__notifymcp__cherry-tools__notify
  • mcp__claw__configmcp__cherry-tools__config

Documented in v2-refactor-temp/docs/breaking-changes/2026-07-03-remove-soul-mode-toggle.md; no user data migration is required.

Special notes for your reviewer

  • Review focus: whether dropping compatibility aliases for mcp__claw__* is acceptable, whether all Claude Code Agents should default to autonomy tools, and whether the preserved assistant-role AskUserQuestion special case is still correct.
  • git log shows the moved engine files as renames (cherryclaw/prompt.tsagents/prompt.ts, etc.) — review with rename detection on.
  • Verification on the final rebased tree: pnpm lint, pnpm test, and pnpm build:check pass locally. GitHub CI is green: changes, catalog-hand-edit-check, changeset-check, basic-checks, general-test, and render-test passed (notify skipped).

Checklist

This checklist is not enforcing, but it's a reminder of items that could be relevant to every PR.
Approvers are expected to review this list.

  • Branch: This PR targets the correct branch — main for active development, v1 for v1 maintenance fixes
  • PR: The PR description is expressive enough and will help future contributors
  • Code: Write code that humans can understand and Keep it simple
  • Refactor: You have left the code cleaner than you found it (Boy Scout Rule)
  • Upgrade: Impact of this change on upgrade flows was considered and addressed if required
  • Documentation: A user-guide update was considered and is present (link) or not required. Check this only when the PR introduces or changes a user-facing feature or behavior.
  • Self-review: I have reviewed my own code (e.g., via /gh-pr-review, gh pr diff, or GitHub UI) before requesting review from others

Release note

Removed the Soul Mode (Autonomous Mode) toggle from agent settings — all agents now always have autonomy features (scheduled tasks, channels, cron/notify/config tools, agent-memory tools, workspace persona prompt), and the permission mode setting is now independent of autonomy. Action required only for integrations that referenced old MCP tool names: use mcp__cherry-tools__cron/notify/config instead of mcp__claw__cron/notify/config.

@vaayne vaayne requested a review from a team July 3, 2026 15:40
@vaayne vaayne requested review from 0xfullex and DeJeune as code owners July 3, 2026 15:40
vaayne added a commit that referenced this pull request Jul 3, 2026
…ntry

The commit hashes recorded before the rebase onto origin/main are stale;
point the entry at the PR instead.

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
@vaayne vaayne marked this pull request as draft July 3, 2026 15:53
vaayne added a commit that referenced this pull request Jul 4, 2026
…n registry design doc

The soul-mode entry now lists all PR #16726 commits and documents the
interactive-tool overlay removal (agents regain AskUserQuestion,
plan-mode, and worktree tools). The declarative-tool-registry design doc
gets a status note marking soul de-gating and the claw→cherry-tools
merge as landed so its claw/soul references read as history.

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
vaayne added a commit that referenced this pull request Jul 6, 2026
…ntry

The commit hashes recorded before the rebase onto origin/main are stale;
point the entry at the PR instead.

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
vaayne added a commit that referenced this pull request Jul 6, 2026
…n registry design doc

The soul-mode entry now lists all PR #16726 commits and documents the
interactive-tool overlay removal (agents regain AskUserQuestion,
plan-mode, and worktree tools). The declarative-tool-registry design doc
gets a status note marking soul de-gating and the claw→cherry-tools
merge as landed so its claw/soul references read as history.

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
@vaayne vaayne force-pushed the remove-cherryClaw branch from e65ef2b to 4b07039 Compare July 6, 2026 06:34
@vaayne vaayne marked this pull request as ready for review July 6, 2026 06:34
vaayne added 16 commits July 6, 2026 14:43
…n process

All agents now run with autonomy enabled, so the soul_enabled branches
collapse: claw + agent-memory MCP servers are always injected, the
mcp__claw__ auto-approve prefix and allowlist wildcards always apply,
AGENT_DISALLOWED_TOOLS (renamed from SOUL_MODE_DISALLOWED_TOOLS) is
always spread, and buildSystemPrompt always uses the PromptBuilder
workspace prompt (the preset claude_code path became unreachable).
AgentTaskService drops the soul/bypassPermissions eligibility gate;
the claw config tool no longer reports soul_enabled.

Renderer, zod schema, i18n, and CherryClaw branding follow in
subsequent commits.

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
…schema

With soul-mode behavior unconditional in the main process, the flag has
no remaining effect: remove the SoulModeField toggle, the soulEnabled
form state and its bidirectional coupling with bypassPermissions
(permission_mode is now a fully independent setting), the soul-gated
composer placeholder branch, the soul_enabled: true seeds on agent
creation presets, the scheduled-task agent eligibility filter, and the
channels soulModeRequired warning. soul_enabled is removed from the
configuration zod schema; the schema is .loose() so stored configs
keep parsing with the stale key ignored.

Orphaned i18n keys are removed in a follow-up commit.

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
…gents engine

The former cherryclaw/ directory is the default prompt/heartbeat engine
for every agent now, so its three modules (prompt, seedWorkspace,
heartbeat) move up into src/main/ai/agents/. Delete the dead
seedWorkspaceTemplates and buildToolGuidance paths and simplify
composeToolGuidance (claw guidance always applies). De-brand prompt and
channel-security wording, drop the CherryClaw docs set and its image,
and update architecture doc trees.

Kept intentionally: the 'cherry-claw' → 'claude-code' read-time type
remap in AgentService and the 'cherry-claw-default' id remap migrator —
both are data-migration shims for existing databases. agent.cherryClaw.*
i18n keys are renamed in the next commit.

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
…rryClaw namespace

Delete the orphaned soul keys (library.config.agent.field.soul_enabled.*,
agent.settings.soulMode.*, the channels soulModeRequired warning, the
obsolete permission_mode.help text, and the unreferenced
agent.cherryClaw soul/sandbox/scheduler/warning/heartbeat groups) and
rename agent.cherryClaw.channels.* / agent.cherryClaw.tasks.* to
agent.channels.* / agent.tasks.* across all twelve locale files, with
code references updated. agent.input.soul_placeholder becomes
agent.input.agent_placeholder (the {{key}} placeholder variant is still
used by the missing-agent composer, so both keys remain). Brand-suffixed
strings like "bypassPermissions (Soul)" are neutralized, and lingering
soul-mode comment references in main are cleaned up.

Adds the breaking-changes entry documenting the removed Autonomous Mode
toggle.

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
…ntry

The commit hashes recorded before the rebase onto origin/main are stale;
point the entry at the PR instead.

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
…verlay

Remove AGENT_DISALLOWED_TOOLS (the former soul-mode overlay): agents
keep AskUserQuestion, plan-mode, and worktree tools. Cron*, TodoWrite,
and NotebookEdit stay blocked regardless — they are classified
exposure: 'disabled' in the declarative tool registry, which was always
the authoritative layer for them. The assistant role's pre-existing
AskUserQuestion disable is restored explicitly.

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
Last CherryClaw branding residue: the in-process autonomy MCP server
'claw' becomes 'cherry', so its tools are now mcp__cherry__cron /
mcp__cherry__notify / mcp__cherry__config. Registry keys, the
auto-approve prefix and allowlist wildcard, condition predicates,
prompts (bootstrap + channel security + tool guidance), the
agent.tools.builtin.CherryCron i18n label key, and tests follow.

Prefix matching keeps the trailing __ delimiter, so mcp__cherry__ and
mcp__cherry-tools__ cannot shadow each other; a regression test guards
this. Agents whose persisted disabledTools contain the old
mcp__claw__cron name lose that override (accepted pre-release).

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
Fold the standalone cherry MCP server (cron/notify/config) into
cherry-tools so all injected builtin tools live under one server and
one mcp__cherry-tools__ namespace. The autonomy handlers move verbatim
to cherryAutonomyTools.ts; CherryBuiltinToolsServer registers them only
when constructed with a per-session agent context. cron/notify/config
join the explicit auto-approve list (they were blanket-allowed as
mcp__cherry__* before), while kb_manage keeps its per-call approval.

Pre-release break: persisted disabledTools entries using the old
mcp__cherry__cron name lose their override; no remap shim added.

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
… merge

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
The server's only production call site (settingsBuilder) always has the
session's agent context, so the optional no-context mode was speculative
flexibility with zero users. Make the context required and drop the
conditional autonomy-tool registration plus its dead-mode test. Also fix
the builtinTools comment: the tool registry hardcodes the runtime names
and does not import these constants.

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
The branch deleted seedWorkspaceTemplates, leaving only the bootstrap
instructions and the SOUL.md content threshold — nothing in the file
seeds a workspace anymore.

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
…n registry design doc

The soul-mode entry now lists all PR #16726 commits and documents the
interactive-tool overlay removal (agents regain AskUserQuestion,
plan-mode, and worktree tools). The declarative-tool-registry design doc
gets a status note marking soul de-gating and the claw→cherry-tools
merge as landed so its claw/soul references read as history.

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
Soul-mode removal made the soul example prompt ('Help me connect to
Telegram') the placeholder for every agent session, dropping the send
shortcut and slash-search hints. Restore the generic placeholder and
delete the now-orphaned agent.input.agent_placeholder key from all
locales.

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
Signed-off-by: Vaayne <liu.vaayne@gmail.com>
Signed-off-by: Vaayne <liu.vaayne@gmail.com>
Signed-off-by: Vaayne <liu.vaayne@gmail.com>
@vaayne vaayne force-pushed the remove-cherryClaw branch from 4b07039 to a8e1a84 Compare July 6, 2026 06:49
vaayne and others added 3 commits July 6, 2026 15:34
Signed-off-by: Vaayne <liu.vaayne@gmail.com>
update_channel / remove_channel / reconnect_channel took a raw channel_id
and mutated any agent's channel. The config tool is auto-approved and now
injected for every agent, so reject foreign channels with the same
"not found" error as missing ones (no existence leak).

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
vaayne added 2 commits July 7, 2026 16:33
The config fields are allowed_chat_ids (allowed_channel_ids on
Discord); allow_ids does not exist.

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
…hema

add_channel validates with ChannelConfigSchema, which requires full
feishu/wechat credentials up front — the QR-code-on-omission flow the
descriptions promised can never be reached; point QR reconnection at
reconnect_channel or the settings UI instead. Also state that enabled
defaults to true only on add and is left unchanged when omitted on
update.

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
@vaayne

vaayne commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator Author

Review follow-up — all four findings addressed (36bc41a5be..802dbcf0b6)

Thanks @eeee0717. Both blockings and both suggestions are fixed at head 802dbcf0b6, plus additional hardening from an independent re-review pass over the same surface.

Blocking 1 — busy branch drops headless (64e050717b + 1184bda149)

Headless is now a per-turn property enforced at fire time, not only a baked connection setting:

  • The busy branch passes { headless: req.headless === true } into enqueueUserMessage(), which marks the message id in a per-session headlessMessageIds set (mirroring steerMessageIds). startNextTurn consumes the mark, so a queued channel message runs as a headless turn even on a warm interactive connection.
  • canUseTool in settingsBuilder denies AskUserQuestion whenever AgentSessionRuntimeService.isCurrentTurnHeadless(sessionId) is true, instructing the model to proceed and state assumptions. The baked disallowedTools entry stays as a second layer for turns headless from the start.
  • Steer continuations inherit correctly (1184bda149): at the steer boundary we compute whether the interrupted turn and every steering input were headless; only then does the roll continuation stay headless. Any interactive participant makes the continuation interactive — a responder exists.

Tests cover the enqueue path, the fire-time denial, and all three roll combinations (headless+headless, headless+interactive, interactive+headless).

Blocking 2 — channel agentId rebind keeps task subscriptions (9ef6050390 + a39455d652)

Fixed on both sides of the boundary:

  • Data hygiene: rebinding a channel's agentId clears its agent_channel_task subscriptions; if syncChannel fails and the old agentId is restored, subscriptions created during the transient window are cleared again (a39455d652).
  • Authoritative delivery guard: runAgentTask filters subscribed channels by channel.agentId === agentId before sending, so wrong-agent delivery is impossible even if a stale row survives.

Suggestions

  • channel_ids "auto-bind all agent channels" (6d5c1c1c6d) — the schema text now matches actual behavior: explicit ids, else the current source channel, else no subscriptions.
  • Permission mode can't be cleared back to inherit (5c3f95499b) — the inherit option now emits null, which survives the PATCH filter.

Additional hardening (independent re-review at the same head)

  • 3e8a56b0d1 — the v1 migrator no longer imports agent_channel_task rows whose channel owner differs from the task owner (same cross-agent invariant as Blocking 2, at the import boundary); skips are logged as skippedCrossAgentChannelLinks.
  • b873ed53ee — heartbeat runs skip the assistant builtin role instead of creating pointless sessions.
  • a6f1e95a5b — proxy URLs are credential-redacted before injection into the assistant system prompt (pre-existing leak of basic-auth userinfo to the provider).
  • 7df2c6d061 — the bootstrap completion log uses tags: ["bootstrap"] (the memory API ignores the singular tag, so the entry was written untagged), and the prompt no longer contradicts itself about FACT.md write paths (memory tool only).
  • b61a978f15/whoami help names the real config fields (allowed_chat_ids / allowed_channel_ids); allow_ids doesn't exist.
  • 802dbcf0b6 — feishu/wechat add_channel descriptions no longer promise a QR-on-omitted-credentials flow that the strict ChannelConfigSchema makes unreachable; QR reconnection points at reconnect_channel / settings UI, and the enabled default semantics are stated accurately.

Pre-existing issues found but out of scope for this PR (follow-ups to be filed): plan/small model clear writes undefined and never persists (agentForm.ts), per-field save clobbers in-progress drafts in ChannelForms, timeoutMinutes: null treated as keep-old in AgentTaskService, stale agent_task tables section in data-cluster.md.

Verification

pnpm lint, pnpm test, pnpm build:check all green locally at 802dbcf0b6.

@vaayne vaayne marked this pull request as draft July 7, 2026 10:06
@vaayne vaayne marked this pull request as ready for review July 7, 2026 10:06
Signed-off-by: Vaayne <liu.vaayne@gmail.com>

# Conflicts:
#	src/renderer/pages/settings/__tests__/TasksSettings.test.tsx

@AtomsH4 AtomsH4 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

代码审查未发现新的 blocker/warning。

范围:移除 Soul Mode / CherryClaw 品牌和开关,把 autonomy tools、agent-memory、workspace prompt、channel/task eligibility 变为所有 agent 默认能力,并清理相关文档、i18n、settings UI 和测试。

影响:用户不再能按 agent 关闭原 Soul Mode 能力,旧 mcp__claw__* 命名/入口被移除。当前 GitHub merge state 仍是 DIRTY/有冲突,需要先同步 main 解决冲突后再重新确认最终 diff;因此这次先不 approve。

Signed-off-by: Vaayne <liu.vaayne@gmail.com>

@kangfenmao kangfenmao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review summary

Solid, well-tested PR. I found no cross-agent isolation break and no data-loss issue — cross-agent channel/task scoping is defense-in-depth and meaningfully tested, the i18n flattening is complete across all 12 locales, and the guard tests genuinely fail if a guard is removed.

The findings below are refinements, concentrated on one theme: now that every agent can run headless (scheduled / channel dispatch), a few "no interactive responder" stall gaps and an enlarged auto-approve blast radius deserve a second look. None are blockers; #1 is the most clearly actionable.


1. [Medium] Headless runs can still stall on plan-mode / worktree tools

src/main/ai/runtime/claudeCode/settingsBuilder.ts (disallowedTools, ~L835) · src/shared/ai/claudecode/constants.ts

The removed SOUL_MODE_DISALLOWED_TOOLS blocked 9 interactive tools. Only 3 (Cron* / TodoWrite / NotebookEdit) are re-covered by the registry's exposure:'disabled', and AskUserQuestion got a dedicated headless denial — but EnterPlanMode / ExitPlanMode / EnterWorktree (all exposure:'internal') have no headless guard. A scheduled/channel run that enters plan mode waits forever for an approval no one gives — exactly the stall the AskUserQuestion denial was added to prevent. Reads as an oversight rather than an intended widening.

Suggested fix: extend the isHeadless disallow branch to also block EnterPlanMode / ExitPlanMode (and consider EnterWorktree), mirroring the AskUserQuestion treatment.

2. [Medium] Interactive follow-up on a headless-baked warm connection silently loses AskUserQuestion

src/main/ai/runtime/claudeCode/settingsBuilder.ts (baked disallowedTools) + src/main/ai/agentSession/AgentSessionRuntimeService.ts (admitTurn / startNextTurn)

disallowedTools is baked into the SDK query once at connect time, keyed on the connection's headless flag. If an interactive UI message lands on a session while a headless turn is still running, the interactive follow-up rides the same headless-baked connection and never sees AskUserQuestion. The per-turn canUseTool gate can only add denials, not restore a baked-out tool. It self-heals once the session goes fully idle (the connectionHeadless !== headless mismatch rebuild), so the window is "interactive message arriving while a headless turn is busy, then back-to-back follow-ups."

Suggested fix: apply the same headless-mismatch teardown/rebuild at the busy-turn boundary, or drop the baked AskUserQuestion disallow and rely solely on the per-turn canUseTool gate (which already handles both directions symmetrically).

3. [Medium — by design, but broader now] Auto-approved config/cron/notify reachable by untrusted channel input on every agent

src/main/ai/runtime/claudeCode/settingsBuilder.ts (auto-approve prefix) · src/main/ai/mcp/servers/cherryAutonomyTools.ts

A third party who can message the agent over a connected IM channel can, via prompt injection, drive auto-approved config add_channel / remove_channel / rename, cron add, or notify (workspace file exfil to the attacker's chat) with no user approval. This stays correctly scoped to the messaged agent (no cross-agent break), but the PR broadens the surface from opt-in Soul agents to all agents, so the blast radius grew. The accepting comment near the auto-approve prefix acknowledges this.

Suggested mitigation: gate the mutating config actions (add_channel / remove_channel / rename) behind approval even when the read/notify/cron tools stay auto-approved, or deny them when the current turn is headless (mirroring the headless AskUserQuestion denial).

4. [Medium] Proxy-credential redaction — scheme-less edge leak + untested call-site wiring

src/main/ai/runtime/claudeCode/settingsBuilder.ts (redactProxyUrlForAssistantContext, ~L167 and its call site ~L213)

(a) app.proxy.url is free-text. For a scheme-less value like user:pass@host:8080, new URL() does not throw — it parses user: as the scheme, so username/password come back empty, the early return proxyUrl fires, and the raw credential lands in the assistant system prompt. (b) The function is unit-tested in isolation, but no test asserts the assembled context string is actually redacted — deleting the wrapper at the call site leaks credentials with every test still green.

Suggested fix: apply the userinfo-stripping regex unconditionally as a belt (not only in the catch branch), and add a buildClaudeCodeSessionSettings-level test asserting the produced context contains - Proxy: http://host and not the password.

5. [Low] Auto-approve is fail-open for future cherry-tools

src/main/ai/tools/adapters/claudeCode/cherryBuiltinApproval.ts · src/main/ai/tools/adapters/claudeCode/agentTools.ts

Auto-approval is prefix-based (mcp__cherry-tools__) minus a hardcoded kb_manage exception. Any tool later added to the cherry-tools server is auto-approved by default; a future mutating tool ships auto-approved unless the author remembers the exception, and no test would fail.

Suggested fix: invert to an explicit auto-approve allowlist, or add a test that every tool the cherry-tools server registers is classified into exactly one of the two lists. (Prefix shadowing is verified safe — trailing __, no standalone cherry server remains.)

6. [Low] Two one-line test gaps

  • src/main/ai/runtime/claudeCode/__tests__/buildMcpServers.test.ts only positively checks cherry-tools/agent-memory are present; add expect(Object.keys(result)).not.toContain('skills') to lock in the "defined-but-unwired" invariant the docs describe.
  • src/main/ai/runtime/claudeCode/__tests__/settingsBuilder.test.ts asserts the headless build's disallowedTools contains AskUserQuestion but not that notify survives; add not.toContain('mcp__cherry-tools__notify').

7. [Low — confirm intended] SKILLS_GUIDANCE prompt block dropped while the skills tool stays injected

src/main/ai/agents/prompt.ts

composeToolGuidance() no longer emits the skills usage guidance, but mcp__skills__skills remains in the registry (exposure:'internal') and its server is still built. The agent keeps the tool but loses all instructions on when/how to use it. Tangential to the "de-brand CherryClaw" scope — please confirm it's intentional.

Nits

  • The config tool renders as "Agent Config" in the edit dialog (agent.tools.builtin.CherryConfig.label) but the hardcoded fallback in MCP_TOOL_LABELS is "Configuration" — same tool, two names.
  • headlessMessageIds can retain stale ids on a turn-error path (bounded, cosmetic).
  • The catch-branch redaction regex [^/@\s]+@ leaves a fragment on a malformed URL whose password contains an unencoded @ (extreme edge).

Strengths worth calling out

  • Cross-agent isolation: every channel-id path validates channel.agentId === agentId with a non-existence-leaking "not found"; delivery re-validates at fire time; rebinds clear stale subscriptions (fail-safe on sync failure); the migrator skips cross-agent legacy rows — all with regression-catching tests (not.toHaveBeenCalled on the downstream mutation, plus a real file-backed integration test).
  • Headless enforcement: cold / idle-reuse / in-flight-connecting rebuild paths are all correct and individually tested; resume token survives via DB re-hydration; rollHeadless steer semantics are exactly right. No stall gap for AskUserQuestion itself (only the plan-mode siblings in #1).
  • i18n & docs: renamed keys present in all 12 locales including the dynamic notify/config tool-card keys; breaking-changes entry matches the final code; tool-registry/data-cluster docs correctly mark skills as unwired and notify/config as user-exposed; no broken links to the deleted cherryclaw/ docs.

Review performed read-only against the PR head; no code was changed.

Signed-off-by: Vaayne <liu.vaayne@gmail.com>

@AtomsH4 AtomsH4 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This review was translated automatically.

Reviewed the previously blocking item: PR is no longer in DIRTY/conflict state, all GitHub checks for the current head have completed and passed. Approve.


Original Content

已复核上次阻塞项:PR 已不再是 DIRTY/冲突状态,当前 head 的 GitHub checks 全部完成通过。Approve.

@kangfenmao kangfenmao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up review — after fix(agent-runtime): tighten headless tool guards (64f19ff)

Thanks for the fast turnaround — this commit addresses my earlier review well:

  • #1 headless plan-mode/worktree stall → fixed (HEADLESS_INTERACTIVE_TOOLS covers AskUserQuestion / EnterPlanMode / ExitPlanMode / EnterWorktree).
  • #2 interactive follow-up losing AskUserQuestion on a headless-baked warm connection → fixed by dropping the baked headless disallow and enforcing per-turn in canUseTool.
  • #3 untrusted-channel config mutation → hardened with headlessConfigMutationHook.
  • #4 proxy-credential redaction → scheme-less case + assembled-context wiring both covered.
  • #5 auto-approve fail-open prefix → switched to an explicit allowlist with a regression test.
  • #6 the two missing test assertions → added.

One new gap the refactor introduces, plus one cleanup:

[Medium] Headless interactive-tool denial is skipped under bypassPermissions / acceptEdits

src/main/ai/runtime/claudeCode/settingsBuilder.ts

This commit moved the interactive-tool denial out of disallowedTools and into canUseTool (the per-turn gate). But the SDK skips canUseTool for auto-approved paths — this file's own comment (~L799) states it: "the SDK skips canUseTool for auto-approved paths (bypassPermissions / acceptEdits / default safe-tools), but PreToolUse hooks fire on every tool call regardless of permission mode." That is exactly why the sibling config-mutation guard in this same commit is a PreToolUse hook rather than a canUseTool branch.

Since permissionMode: agentConfig?.permission_mode (L333) passes the user-set mode through unmodified, a scheduled/channel (headless) agent can run in bypassPermissions — which is in fact the mode the old Soul Mode used to force, so migrated autonomy agents plausibly carry it. In that case, for a non-assistant agent, all three enforcement layers miss:

  • disallowedTools — no longer lists these tools for non-assistant agents (the isHeadless branch was removed here),
  • canUseTool — skipped under bypass/acceptEdits,
  • PreToolUse hooks — none registered for the interactive tools (only for config mutation).

Net: AskUserQuestion / EnterPlanMode / ExitPlanMode / EnterWorktree become reachable again in a headless bypass run → the exact stall #1 set out to prevent. This is also a partial regression from the pre-commit state, where the isHeadless branch hard-baked AskUserQuestion into disallowedTools (which does apply under bypass).

Suggested fix: enforce the interactive-tool denial via a PreToolUse hook too (mirroring headlessConfigMutationHook, which fires regardless of permission mode), keeping the canUseTool branch as well so interactive follow-ups can still recover. That makes the two headless guards symmetric and closes the bypass hole without reopening #2.

Worth confirming: whether AskUserQuestion counts as a "default safe-tool" that also skips canUseTool in default mode — if so, the gap is wider than just bypass/acceptEdits.

[Low / cleanup] headless option and the connection-rebuild machinery may now be dead

buildClaudeCodeSessionSettings still accepts headless?: boolean (settingsBuilder.ts:258) but nothing in the function reads it anymore — the baked settings no longer vary by headless. The connectionHeadless mismatch teardown/rebuild in AgentSessionRuntimeService was added specifically because "the AskUserQuestion disallow is settable only at connect time"; that rationale is gone now that enforcement is per-turn. Worth confirming and removing the now-redundant option + rebuild path (and its tests) to avoid leaving dead complexity.

Review performed read-only against the PR head; no code was changed.

vaayne added 2 commits July 9, 2026 10:04
Move the headless interactive-tool denial into a PreToolUse hook
(headlessInteractiveToolHook) beside the existing canUseTool branch so it
also fires under bypassPermissions/acceptEdits, where the SDK skips
canUseTool. Without it a migrated autonomy agent running in bypass mode could
reach AskUserQuestion/EnterPlanMode/ExitPlanMode/EnterWorktree in a headless
scheduled/channel run and stall on a prompt no one answers.

With enforcement now fully per-turn (canUseTool + hook, resolved by session id
via isCurrentTurnHeadless), the connect-time `headless` plumbing no longer
varies any baked setting. Drop the dead path: headless on ClaudeCodeSessionOptions
and AgentRuntimeConnectInput, entry.headless / connectionHeadless, and the idle
non-headless-connection mismatch rebuild — a warm connection is always safe to
reuse now, keeping its resume token.

Addresses review of PR #16726 (pullrequestreview-4654207072).

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
Signed-off-by: Vaayne <liu.vaayne@gmail.com>

# Conflicts:
#	src/main/ai/agentSession/AgentSessionRuntimeService.ts
#	src/main/ai/agentSession/__tests__/AgentSessionRuntimeService.test.ts

@kangfenmao kangfenmao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — approving.

All review items across the three rounds are resolved:

  • Initial review (#1 headless plan-mode/worktree stall, #2 warm-connection AskUserQuestion loss, #3 auto-approved config-mutation surface, #4 proxy-credential redaction, #5 fail-open auto-approve prefix, #6 test-assertion gaps) — all fixed.
  • Follow-up (interactive-tool denial skipped under bypassPermissions/acceptEdits, plus the now-dead headless connection plumbing) — fixed in 5d0b762: the denial is now a PreToolUse hook (fires in every permission mode) beside the retained canUseTool branch, both resolved per-turn by session id via isCurrentTurnHeadless; the redundant connect-time headless plumbing and mismatch-rebuild were cleanly removed with per-turn enforcement preserved and tests updated.

CI is green (basic-checks, catalog-hand-edit-check, changes, changeset-check, general-test, render-test).

One non-blocking confirmation (not required for merge): the SKILLS_GUIDANCE prompt block was dropped while the mcp__skills__skills tool stays injected — please confirm that leaving the tool available without usage guidance is intentional. Not a merge blocker.

Nice work on the cross-agent scoping and the headless-enforcement hardening.

@kangfenmao kangfenmao added the ready-to-merge This PR has sufficient reviewer approvals but is awaiting code owner approval. label Jul 9, 2026

@0xfullex 0xfullex left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes for five confirmed issues:

  1. [Blocking] settingsBuilder.ts:82 and cherryAutonomyTools.ts:769 leave bootstrap config writes available in headless turns. HEADLESS_CONFIG_MUTATION_ACTIONS denies rename and channel mutations, but complete_bootstrap and reset_bootstrap also call agentService.updateAgent() to mutate agent.configuration.bootstrap_completed. Since bootstrap instructions explicitly ask the model to call config complete_bootstrap, a scheduled or channel run can still mutate agent configuration even though the headless hook says these turns cannot mutate configuration. Please add both bootstrap actions to the headless deny set, or document them as intentional exceptions and adjust the policy/tests.

  2. [Blocking] AgentChannelWorkflowService.ts:72 loses existing task subscriptions when a channel rebind fails. The code clears subscriptions when agentId changes, then if syncChannel() throws it restores the channel row to the old agent and clears subscriptions again. That leaves a failed update with the original channel owner restored but all pre-existing agent_channel_task rows deleted. Please snapshot and restore the old subscriptions on rollback, or make the rebind and subscription cleanup rollbackable as one operation.

  3. [Blocking] AgentTaskService.ts:202 can partially update a task when subscription replacement fails. updateJobSchedule() commits the schedule/name/prompt/trigger changes before replaceTaskSubscriptions() runs. If subscription replacement throws, the API returns an error but the task fields remain updated while channels remain old. createTask already rolls back the registered schedule on this failure path, but updateTask has no equivalent rollback. Please wrap the schedule update and subscription replacement in a single atomic write, or explicitly restore the previous schedule when the subscription write fails.

  4. [Important] TasksSettings.tsx:328 keeps hidden stale channelIds in the task detail state. The UI filters the selector options to channels owned by the task agent, but initializes and resyncs channelIds from task.channelIds without filtering. The shared Combobox keeps unknown selected values in its controlled array, so when the user changes visible channel selections the PATCH can still include a hidden foreign or missing id. The service now rejects that id, which means the user cannot clear the stale value from this UI. Please normalize channelIds against the visible/owned taskChannels on load and before save.

  5. [Important] zh-cn.json:41 / zh-tw.json:41 and zh-cn.json:661 / zh-tw.json:661 contain empty user-visible error strings. agent.channels.createError, deleteError, updateError, agent.tasks.error.loadFailed, and agent.tasks.logs.loadError are blank in both Chinese locale files while English has real text. The affected toasts and empty states render as blank messages in Chinese. Please fill these migrated locale keys with non-empty localized text.

@0xfullex 0xfullex removed the ready-to-merge This PR has sufficient reviewer approvals but is awaiting code owner approval. label Jul 9, 2026
vaayne added 5 commits July 9, 2026 17:31
complete_bootstrap and reset_bootstrap mutate agent.configuration via
updateAgent() just like rename/channel actions, but were missing from
HEADLESS_CONFIG_MUTATION_ACTIONS, letting scheduled/channel turns flip
bootstrap state despite the headless no-config-mutation policy.

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
agent.channels.createError/deleteError/updateError,
agent.tasks.error.loadFailed and agent.tasks.logs.loadError were empty
in zh-CN/zh-TW, rendering blank toasts and empty states.

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
channelIds state was fed to the channel Combobox unfiltered, and the
Combobox toggles against its controlled array, so ids no longer owned by
the task's agent silently rode along in every PATCH — which the service
now rejects, leaving the value uncleanable from the UI. Pass a value
derived from owned channels so stale ids never reach the selector or
the payload.

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
…ent fails

updateTask committed name/trigger/template changes via updateJobSchedule
before replaceTaskSubscriptions ran, so a subscription failure returned
an error while leaving the task half-updated. Mirror createTask's
rollback: restore the fields the patch touched from the pre-update
snapshot, then rethrow.

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
…olls back

A failed agentId rebind restored the channel row to its original agent
but left the pre-existing agent_channel_task rows deleted. Snapshot the
subscriptions before clearing and re-subscribe them once the row restore
succeeds; only a failed restore keeps them cleared, since the channel may
then still point at the new agent.

Signed-off-by: Vaayne <liu.vaayne@gmail.com>
@vaayne

vaayne commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator Author

@0xfullex Thanks for the thorough review — all five findings were real. Fixed in five commits, one per finding:

  1. Headless bootstrap config writes7bb0387 adds complete_bootstrap / reset_bootstrap to HEADLESS_CONFIG_MUTATION_ACTIONS. The headless deny test now runs all three mutation actions and asserts status stays allowed.
  2. Lost subscriptions on failed rebind rollbackb68e3ec snapshots the channel's task subscriptions before the conservative clear and re-subscribes them once the row restore succeeds. Subscriptions stay cleared only when the row restore itself fails — the one branch where the wrong-agent-delivery rationale actually holds; the old comment claimed more than that and has been rewritten. Two new tests cover both branches.
  3. Partial update in updateTaske7ef91a mirrors createTask's rollback: when replaceTaskSubscriptions throws, the schedule fields the patch touched are restored from the pre-update snapshot and the error is rethrown. New test asserts the restore payload.
  4. Stale hidden channelIds in task detailf9bfa22 derives the Combobox value from agent-owned channels, so stale ids never enter the controlled array nor the PATCH — and the user can now clear them just by touching the selection. New test: a task with one owned + one foreign id produces a payload without the foreign id.
  5. Empty zh locale strings6aa13e9 fills all five keys in zh-CN and zh-TW; pnpm i18n:check passes.

Verified with the targeted vitest suites plus a full pnpm build:check (typecheck + i18n + full test run, all green).

Reflection — why these survived three earlier review rounds: four of the five are edges of previous fixes rather than untouched bugs.

  • The headless deny set was built by enumerating the actions the earlier reviewer named, instead of deriving it from the invariant "anything that mutates agent configuration" — the bootstrap actions mutate config but weren't on the list.
  • The subscription loss was introduced by the earlier rebind-safety fix, which optimized for never delivering to the wrong agent and accepted data loss on a branch (successful rollback) where that rationale doesn't apply.
  • The updateTask gap is a rollback that was added to createTask in a previous round but never mirrored to its symmetric update path.
  • The stale-channelIds UI state is older latent behavior that only became user-visible once the new server-side ownership validation started rejecting it.
  • The i18n blanks came from i18n:sync auto-creating keys with empty values, which i18n:check doesn't flag (possible follow-up: make the checker flag empty strings for keys that are non-empty in en-US).

Takeaways we're applying going forward: patch the invariant, not the reviewer's enumeration; sweep symmetric create/update paths when fixing one of them; and when adding validation, check what it does to already-persisted state and the UI that edits it.

# Conflicts:
#	src/renderer/i18n/translate/de-de.json
#	src/renderer/i18n/translate/el-gr.json
#	src/renderer/i18n/translate/es-es.json
#	src/renderer/i18n/translate/fr-fr.json
#	src/renderer/i18n/translate/ja-jp.json
#	src/renderer/i18n/translate/pt-pt.json
#	src/renderer/i18n/translate/ro-ro.json
#	src/renderer/i18n/translate/ru-ru.json
#	src/renderer/i18n/translate/vi-vn.json
@vaayne vaayne requested a review from 0xfullex July 9, 2026 09:51
@kangfenmao kangfenmao added the ready-to-merge This PR has sufficient reviewer approvals but is awaiting code owner approval. label Jul 9, 2026
@0xfullex 0xfullex merged commit 6aee3d7 into main Jul 9, 2026
7 checks passed
@0xfullex 0xfullex deleted the remove-cherryClaw branch July 9, 2026 10:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge This PR has sufficient reviewer approvals but is awaiting code owner approval.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants